using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Reflection;
using System.Windows.Forms;
using System.Data;
using System.Collections.Specialized;
using System.Diagnostics;

namespace eyeArchive.Core
{
	public class Kernel
	{

		#region "Members"
		private string version;
		private SqlVersions sqlVersion;

		private SqlConnectionStringBuilder csb;
		// Both list are identical: this is only made to enable databinding on it
		// If the 2 comboboxes uses the same list, they synchronize the selected value
		private List<string> localDatabases;
		private List<string> remoteDatabases;
		private SortedList<string, Table> tables;
		private List<Dependency> dependencies;
		private string localDatabase;
		private string remoteDatabase;
		private bool keepData;
		private bool cleanup;
		#endregion

		#region "Events"
		public event OnNotifyEventHandler OnNotify;
		public event OnPreProcessEventHandler OnPreProcess;
		public event OnProcessEventHandler OnProcess;
		public event OnPostProcessEventHandler OnPostProcess;
		#endregion

		#region "Singleton design-pattern"
		private static Kernel instance = new Kernel();
		public static Kernel Instance
		{
			get { return instance; }
		}
		#endregion

		#region "Constructors"
		private Kernel()
		{
			StringBuilder rc;
			Version v;

			csb = new SqlConnectionStringBuilder();
			csb.ApplicationName = Application.ProductName;
			csb.WorkstationID = Environment.MachineName;

			localDatabases = new List<string>();
			remoteDatabases = new List<string>();
			tables = new SortedList<string, Table>();
			dependencies = new List<Dependency>();
			keepData = true;

			// === Compute the Version value ==========================================
			rc = new StringBuilder();

			v = Assembly.GetExecutingAssembly().GetName().Version;
			rc.AppendLine(String.Format("Core Engine Version : {0}.{1:D2} (Build {2:D3})", v.Major, v.Minor, v.Build, v.Revision));

			v = Assembly.GetEntryAssembly().GetName().Version;
			rc.AppendLine(String.Format("Application Version : {0}.{1:D2} (Build {2:D3})", v.Major, v.Minor, v.Build, v.Revision));

			version = rc.ToString();
		}
		#endregion

		#region "Properties"
		public string Version
		{
			get { return version; }
		}

		public string DataSource
		{
			get { return csb.DataSource; }
		}

		public string InitialCatalog
		{
			get { return csb.InitialCatalog; }
		}

		internal string LocalConnectionString
		{
			get
			{
				csb.InitialCatalog = localDatabase;
				return csb.ConnectionString;
			}
		}

		internal string RemoteConnectionString
		{
			get
			{
				csb.InitialCatalog = remoteDatabase;
				return csb.ConnectionString;
			}
		}

		public string LocalDatabase
		{
			get { return localDatabase; }
		}

		public string RemoteDatabase
		{
			get { return remoteDatabase; }
		}

		public List<string> LocalDatabases
		{
			get { return localDatabases; }
		}

		public List<string> RemoteDatabases
		{
			get { return remoteDatabases; }
		}

		public SortedList<string, Table> Tables
		{
			get { return tables; }
		}

		internal List<Dependency> Dependencies
		{
			get { return dependencies; }
		}

		public SqlVersions SqlVersion
		{
			get { return sqlVersion; }
		}

		public bool KeepData
		{
			get { return keepData; }
			set { keepData = value; }
		}

		public bool CleanUp
		{
			get { return cleanup; }
			set { cleanup = value; }
		}
		#endregion

		#region "Methods"
		public void SetServer(string dataSource, bool integratedSecurity, string userId, string password)
		{
			SqlConnection cn;
			SqlCommand cmd;
			SqlDataReader dr;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			// === Validate the input parameters ===================================
			if (dataSource != null) dataSource = dataSource.ToLower();

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			if (OnNotify != null) OnNotify(this, new NotifyEventArgs("Setting server information"));

			// === Set the connection string builder values ========================
			csb.DataSource = dataSource;
			csb.IntegratedSecurity = integratedSecurity;
			csb.UserID = (integratedSecurity == true ? "" : userId);
			csb.Password = (integratedSecurity == true ? "" : password);
			// --- Force the default catalog ---------------------------------------
			csb.InitialCatalog = "master";

			// === Open the database connection ====================================
			cn = new SqlConnection(csb.ConnectionString);
			cn.Open();
			cmd = cn.CreateCommand();

			// === Retrieve the version of the server ==============================
			// The version of the server is important to determine how to retrieve
			// tables schema's
			if (OnNotify != null) OnNotify(this, new NotifyEventArgs("Retrieving server's version"));
			cmd.CommandText = "EXEC xp_msver ProductVersion";
			dr = cmd.ExecuteReader();
			if (dr.Read() == true)
			{
				string v = dr["Character_Value"].ToString().Substring(0, 1);

				if (Enum.IsDefined(typeof(SqlVersions), int.Parse(v)))
					sqlVersion = (SqlVersions)Enum.Parse(typeof(SqlVersions), v);
				else
					throw new InvalidOperationException(String.Format("The version of the local Data Source [{0}] is not supported by eyeArchive.", csb.DataSource));
			}
			else
				throw new InvalidOperationException(String.Format("The version of the local Data Source [{0}] could not be found.", csb.DataSource));
			dr.Close();

			// === Retrieve the list of databases ==================================
			localDatabases.Clear();
			remoteDatabases.Clear();

			if (OnNotify != null) OnNotify(this, new NotifyEventArgs("Retrieving databases"));
			cmd.CommandText = "SELECT Name FROM sysDatabases WHERE HAS_DBACCESS(Name)=1 AND Name NOT IN ('model','master','tempdb','msdb') ORDER BY Name";
			dr = cmd.ExecuteReader();
			while (dr.Read())
			{
				localDatabases.Add(dr["Name"].ToString());
				remoteDatabases.Add(dr["Name"].ToString());
			}
			dr.Close();

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			// === Cleanup objects =================================================
			if (OnPostProcess != null) OnPostProcess(this, new ProcessEventArgs("Server information retrieved"));

			// === Close the database connection ===================================
			cmd.Connection = null;
			cn.Close();
		}

		public void SetLocalDatabase(string initialCatalog)
		{
			SqlConnection cn;
			SqlCommand cmd;
			SqlDataReader dr;
			string query;
			DataTable dt;
			SqlDataAdapter da;
			int idx;
			Table item;

			///TODO: Verify that local and remote databases are different

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			idx = 0;

			// === Set the initial catalog =========================================
			localDatabase = initialCatalog;

			// === Open the database connection ====================================
			cn = new SqlConnection(LocalConnectionString);
			cn.Open();
			cmd = cn.CreateCommand();
			dt = new DataTable();

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			// === Retrieve the tables =============================================
			tables.Clear();

			query = String.Format("SELECT {1}(uid) AS PrimarySchema, name AS PrimaryTable "
				//+ ",(SELECT {1}(uid) FROM sysObjects WHERE xtype='U' AND name LIKE '{0}' + O.name) AS ForeignSchema "
				//+ ",(SELECT name FROM sysObjects WHERE xtype='U' AND name LIKE '{0}' + O.name) AS ForeignTable "
												+ "FROM sysObjects "
												+ "WHERE xtype='U' "
				//+ "AND (name NOT LIKE '{0}%') "
												+ "ORDER BY PrimarySchema,PrimaryTable",
												Constants.PFX_SCHEMA,
												(sqlVersion == SqlVersions.Sql2000 ? "USER_NAME" : "SCHEMA_NAME"));

			da = new SqlDataAdapter(query, cn);
			da.Fill(dt);
			if (OnPostProcess != null) OnPreProcess(this, new ProcessEventArgs("Retrieving tables information", dt.Rows.Count));
			foreach (DataRow row in dt.Rows)
			{
				idx += 1;
				if (OnProcess != null) OnProcess(this, new ProcessEventArgs(String.Format("Retrieving table structure for table [{0}].[{1}]", row["PrimarySchema"], row["PrimaryTable"]), idx));
				if (row["PrimaryTable"].ToString().StartsWith(Constants.PFX_SCHEMA) == false)
				{
					item = new Table(this, row);
					tables.Add(item.Key, item);
				}
			}

			RefreshArchivePolicyInformation();

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			Table foreign;

			// === Set the dependencies ============================================
			foreach (Table primary in tables.Values)
			{
				cmd.CommandText = String.Format("EXEC sp_FKeys @PKTable_Owner='{0}', @PKTable_Name = '{1}'", primary.PrimarySchema, primary.PrimaryTable);
				Debug.WriteLine(cmd.CommandText);
				dr = cmd.ExecuteReader();
				while (dr.Read())
				{
					// find the foreign key
					foreign = null;
					foreign = tables[string.Format(Constants.TABLE_KEY_FORMAT, dr["FKTable_Owner"].ToString(), dr["FKTable_Name"].ToString())];

					if (foreign != null)
						dependencies.Add(new Dependency(primary, foreign, dr));
				}
				dr.Close();
			}

			// === Cleanup objects =================================================
			if (OnPostProcess != null) OnPostProcess(this, new ProcessEventArgs("Tables information retrieved", dt.Rows.Count));

			// === Close the database connection ===================================
			cmd.Connection = null;
			cn.Close();
		}

		public void SetRemoteDatabase(string initialCatalog)
		{
			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================

			///TODO: Verify that local and remote databases are different

			// === Set the initial catalog =========================================
			remoteDatabase = initialCatalog;

			foreach (Table item in tables.Values)
				item.RefreshArchiveTableInformation();
		}

		/// <summary>
		/// This procedure will try to find out any previous installation of eyeArchive.
		/// It is important to retrieve previous archive conditions so that the installed
		/// policies are not removed.
		/// </summary>
		private void RefreshArchivePolicyInformation()
		{
			SqlConnection cn;
			SqlCommand cmd;
			SqlDataReader dr;
			StringBuilder sbText;
			string text;
			int pos0;
			int pos9;
			string condition;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			// === Initialize variables ============================================

			// === Open the database connection ====================================
			cn = new SqlConnection(LocalConnectionString);
			cn.Open();
			cmd = cn.CreateCommand();

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			// === Process each table ==============================================
			foreach (Table tbl in tables.Values)
			{
				sbText = new StringBuilder();
				cmd.CommandText = string.Format("SELECT text FROM sysObjects, sysComments WHERE sysObjects.id=sysComments.id AND {0}(uid)='{1}' AND name='{2}{3}'",
					(sqlVersion == SqlVersions.Sql2000 ? "USER_NAME" : "SCHEMA_NAME"),
					tbl.PrimarySchema,
					Constants.PFX_PROCEDURE,
					tbl.PrimaryTable);
				dr = cmd.ExecuteReader();
				while (dr.Read())
				{
					sbText.Append(dr["text"].ToString());
				}
				dr.Close();
				text = sbText.ToString();
				if ((text != null) && (text.Length != 0))
				{
					pos0 = text.IndexOf(Constants.TAG_OPEN);
					pos9 = text.IndexOf(Constants.TAG_CLOSE);

					// === Find the condition ==========================================
					if ((pos0 != -1) && (pos0 < pos9))
					{
						pos0 = text.IndexOf("WHERE", pos0);
						condition = text.Substring(pos0 + "WHERE".Length, pos9 - (pos0 + "WHERE".Length)).Replace("\n", "").Replace("\t", "").Trim();
						tbl.SetArchiveStatus(Statuses.Installed, condition);
					}
				}
			}


			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
		}

		internal void TriggerOnNotify(object sender, NotifyEventArgs e)
		{
			if (OnNotify != null) OnNotify(sender, e);
		}

		internal void TriggerOnPreProcess(object sender, ProcessEventArgs e)
		{
			if (OnPreProcess != null) OnPreProcess(sender, e);
		}

		internal void TriggerOnProcess(object sender, ProcessEventArgs e)
		{
			if (OnProcess != null) OnProcess(sender, e);
		}

		internal void TriggerOnPostProcess(object sender, ProcessEventArgs e)
		{
			if (OnPostProcess != null) OnPostProcess(sender, e);
		}

		public string GetSummary()
		{
			System.Text.StringBuilder rc;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			// === Initialize variables ============================================
			rc = new StringBuilder();

			// === Builder the comment text ========================================
			rc.AppendFormat("\r\neyeArchive will run with the following parameters:");
			rc.AppendFormat("\r\n==================================================");
			rc.AppendFormat("\r\n- Database to be archived: {0}@{1}", localDatabase, csb.DataSource);
			rc.AppendFormat("\r\n            MSSQL version: {0}", sqlVersion);
			rc.AppendFormat("\r\n- Archive database       : {0}@{1}", remoteDatabase, csb.DataSource);
			rc.AppendFormat("\r\n            MSSQL version: {0}", sqlVersion);
			rc.AppendFormat("\r\n");
			rc.AppendFormat("\r\nArchiving strategy:");
			rc.AppendFormat("\r\n-------------------");
			foreach (Table item in tables.Values)
			{
				if (item.Action == Actions.Install || item.Status == Statuses.Installed)
				{
					rc.AppendFormat("\r\n   - [{0}].[{1}]: {2}", item.PrimarySchema, item.PrimaryTable, item.Action);
					if (item.Action == Actions.Install)
					{
						if ((item.Condition == null) || (item.Condition.Length == 0))
							rc.AppendFormat(" (No condition given)");
						else
						{
							rc.AppendFormat("\r\n      > With condition(s): {0}", item.Condition);
						}
					}
				}
			}
			return rc.ToString();
		}

		public string GetAllScripts()
		{
			System.Text.StringBuilder rc;
			List<string> script;
			Version ver;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			rc = new StringBuilder();
			script = new List<string>();

			#region Kommentare
			script.Add("/*****************************************************************************");
			AddCommentLine(script, string.Format("{0}", Application.ProductName));
			AddCommentLine(script, new string('-', 73));

			AddCommentLine(script, string.Format("- Script date     : {0}", DateTime.Now));
			AddCommentLine(script, string.Format("- Database to be archived: {0}@{1}", localDatabase, csb.DataSource));
			AddCommentLine(script, string.Format("-           MSSQL version: {0}", sqlVersion));
			AddCommentLine(script, string.Format("- Archive database       : {0}@{1}", remoteDatabase, csb.DataSource));
			AddCommentLine(script, string.Format("-           MSSQL version: {0}", sqlVersion));
			AddCommentLine(script, string.Format(" "));
			AddCommentLine(script, string.Format("Archiving strategy:"));
			AddCommentLine(script, string.Format("-------------------"));
			foreach (Table item in tables.Values)
				AddCommentLine(script, string.Format("   - [{0}].[{1}]: {2}", item.PrimarySchema, item.PrimaryTable, item.Action));

			AddCommentLine(script, string.Format(" "));
			AddCommentLine(script, new string('-', 73));

			ver = Assembly.GetExecutingAssembly().GetName().Version;
			AddCommentLine(script, string.Format("Core Engine Version : {0}.{1:D2} (Build {2:D1} rev. {3:D3})", ver.Major, ver.Minor, ver.Build, ver.Revision));

			ver = new Version(Application.ProductVersion);
			AddCommentLine(script, String.Format("Application Version : {0}.{1:D2} (Build {2:D1} rev. {3:D3})", ver.Major, ver.Minor, ver.Build, ver.Revision));
			script.Add(" *****************************************************************************/");
			#endregion

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			#region ArchiveDB not used
			script.Add("/*****************************************************************************");
			AddCommentLine(script, "Script applied on archive database");
			script.Add(" *****************************************************************************/");
			Constants.AddScriptItem(script, String.Format("USE {0};", remoteDatabase));
			Constants.AddScriptItem(script, String.Format("GO\n"));
			GetScriptSchema(script, false);
			GenerateScript(script, false);
			#endregion

			script.Add("/*****************************************************************************");
			AddCommentLine(script, "Script applied on database to be archived");
			script.Add(" *****************************************************************************/");
			Constants.AddScriptItem(script, String.Format("USE {0};", localDatabase));
			Constants.AddScriptItem(script, String.Format("GO\n"));
			GenerateScript(script, true);
			script.Add("");


			script.Add("/*****************************************************************************");
			AddCommentLine(script, "Script applied on database to be archived");
			script.Add(" *****************************************************************************/");
			Constants.AddScriptItem(script, String.Format("USE msdb;"));
			Constants.AddScriptItem(script, String.Format("GO\n"));
			GenerateJobScript(script, true);
			script.Add("");
			GenerateJobScript(script, false);
			script.Add("");

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			foreach (string line in script)
				rc.AppendLine(line);

			return rc.ToString();
		}

		private void GetScriptSchema(List<string> script, bool isLocal)
		{
			SqlConnection cn;
			SqlCommand cmd;
			int id;
			List<string> scriptedSchemas;
			StringBuilder rc;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			scriptedSchemas = new List<string>();
			rc = new StringBuilder();

			// === Open a database connection ======================================
			cn = new SqlConnection(RemoteConnectionString);
			cn.Open();
			cmd = cn.CreateCommand();

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			if ((isLocal == false) && (sqlVersion != SqlVersions.Sql2000))
			{
				foreach (Table tbl in tables.Values)
					if ((tbl.GetResultingStatus() == Statuses.Installed) && (tbl.PrimarySchema.Equals("dbo") == false) && (scriptedSchemas.Contains(tbl.PrimarySchema) == false))
					{
						cmd.CommandText = string.Format("SELECT ISNULL((SELECT schema_id FROM sys.schemas WHERE name = '{0}'),0)", tbl.PrimarySchema);
						id = int.Parse(cmd.ExecuteScalar().ToString());
						if (id == 0)
						{
							rc.AppendFormat("\nCREATE SCHEMA [{0}]", tbl.PrimarySchema);
							rc.AppendFormat("\nGO");
							rc.AppendFormat("\n");
							scriptedSchemas.Add(tbl.PrimarySchema);
						}
					}
			}

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			// === Close the database connection ===================================
			cmd.Connection = null;
			cn.Close();

			if (rc.Length != 0)
				Constants.AddScriptItem(script, rc.ToString());
		}

		///TODO: Understand why the newly created table remains unknown when creating views and why an intermediate commit force the table to be created in the wrong db (the original one)
		private void RunAllScripts()
		{
			SqlConnection cn;
			SqlCommand cmd;
			SqlTransaction trn;
			List<string> script;
			int i;

			// === Pre-Process
			script = new List<string>();
			i = 0;

			if (OnNotify != null) OnNotify(this, new NotifyEventArgs(string.Format("Generating script to be applied")));
			GenerateScript(script, false);
			GenerateScript(script, true);
			GenerateJobScript(script, true);
			GenerateJobScript(script, false);

			if (OnPreProcess != null) OnPreProcess(this, new ProcessEventArgs(string.Format("Applying script")));
			cn = new SqlConnection(LocalConnectionString);
			cn.Open();

			trn = cn.BeginTransaction();
			cmd = cn.CreateCommand();
			cmd.Transaction = trn;

#if !DEBUG
			try
			{
#endif
			foreach (string line in script)
			{
				i++;
				if (OnProcess != null) OnProcess(this, new ProcessEventArgs(String.Format("Applying script statement #{0}", i), 1));
				if ((line != null) && ((line != "GO") && (line != "\nGO") && (line != "GO\n")))
				{
					Debug.WriteLine(string.Format("Ln. {0}> {1}", i, line));
					cmd.CommandText = line;
					cmd.ExecuteNonQuery();
				}
			}

			if (OnPostProcess != null) OnPostProcess(this, new ProcessEventArgs("Finalizing script"));
#if DEBUG
			if (MessageBox.Show(string.Format("The script has been successfully executed. Would you like to commit the changes?"), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
#endif
			{
				trn.Commit();
				if (OnNotify != null) OnNotify(this, new NotifyEventArgs(string.Format("Script successfully committed")));
			}
#if DEBUG
			else
			{
				trn.Rollback();
				if (OnNotify != null) OnNotify(this, new NotifyEventArgs(string.Format("Script successfully rolled back")));
			}
#endif
#if !DEBUG
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
#endif
#if DEBUG
			Trace.Fail("");
#endif
#if !DEBUG
				if (OnNotify != null) OnNotify(this, new NotifyEventArgs(string.Format("The script has not been committed")));
				trn.Rollback();
			}
#endif
		}

		private void GenerateScript(List<string> script, bool isLocal)
		{
			int i;

			// =====================================================================
			// === Pre-Process                                                   === 
			// =====================================================================
			if (OnPreProcess != null) OnPreProcess(this, new ProcessEventArgs("Scripting table-level logging", tables.Count));
			i = 0;

			// === Reset the flag AlreadyScripted on all tables ====================
			// This flag is used to avoid scripting a same object many times: it can
			// occur when working on dependencies
			foreach (Table item in tables.Values)
				item.AlreadyScripted = false;

			// === Force installation of dependent objects =========================
			ForceInstall();

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			// === Table-level logging =============================================
			foreach (Table t in tables.Values)
			{
				i++;
				if (OnProcess != null) OnProcess(this, new ProcessEventArgs(String.Format("Scripting changes on table [{0}].[{1}]", t.PrimarySchema, t.PrimaryTable), i));
				if (t.GetResultingStatus() == Statuses.Installed)
					if (t.AlreadyScripted == false)
						t.GetScript(script, isLocal);
					else
						throw new Exception();
			}

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			if (OnPostProcess != null) OnPostProcess(this, new ProcessEventArgs("Table-level changes scripted"));
		}

		private void GenerateJobScript(List<string> script, bool isLocal)
		{
			StringBuilder rc;
			string objectName;
			//Table lastTable;
			int lastStep;
			string sourceDatabase;

			// =====================================================================
			// === Pre-Process                                                   === 
			// =====================================================================
			if (OnPreProcess != null) OnPreProcess(this, new ProcessEventArgs("Scripting the job", tables.Count));
			rc = new StringBuilder();


			if (isLocal == true)
			{
				objectName = string.Format("{0}{1}", Constants.PFX_JOB, localDatabase);
				sourceDatabase = localDatabase;
			}
			else
			{
				objectName = string.Format("{0}{1}_restore", Constants.PFX_JOB, localDatabase);
				sourceDatabase = remoteDatabase;
			}

			//lastTable = null;
			lastStep = 0;

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			// === Drop previous job if it exists ==================================
			rc.AppendFormat("\n-------------------------------");
			rc.AppendFormat("\n-- Step 1. Drop previous job --");
			rc.AppendFormat("\n-------------------------------");
			rc.AppendFormat("\nIF  EXISTS (SELECT job_id FROM sysjobs WHERE name = '{0}')", objectName);
			rc.AppendFormat("\nEXEC sp_delete_job @job_name='{0}', @delete_unused_schedule=1;", objectName);
			rc.AppendFormat("\nGO");
			rc.AppendFormat("\n");

			// === Create the job ==================================================
			rc.AppendFormat("\n----------------------------------------");
			rc.AppendFormat("\n-- Step 2. Create the job entry point --");
			rc.AppendFormat("\n----------------------------------------");
			rc.AppendFormat("\nEXEC sp_add_job @job_name='{0}'", objectName);
			rc.AppendFormat("\n");

			// === Add steps to the job ============================================
			///TODO: Take care of dependencies: 
			rc.AppendFormat("\n--------------------------------------------");
			rc.AppendFormat("\n-- Step 3. Add archiving steps to the job --");
			rc.AppendFormat("\n--------------------------------------------");
			foreach (Table tbl in tables.Values)
				if ((tbl.GetResultingStatus() == Statuses.Installed) && (tbl.Condition != null) && (tbl.Condition.Length != 0))
				{
					rc.AppendFormat("\nEXEC sp_add_jobstep @job_name='{0}',", objectName);
					rc.AppendFormat("\n\t@step_name='[{0}].[{1}{2}_{3}]',", sourceDatabase, Constants.PFX_PROCEDURE, tbl.PrimarySchema, tbl.PrimaryTable);
					rc.AppendFormat("\n\t@command='EXEC {0}{1} {2}',", Constants.PFX_PROCEDURE, tbl.PrimaryTable, tbl.GetExecParameters(tbl.PrimarySchema, tbl.PrimaryTable, true));
					rc.AppendFormat("\n\t@database_name='{0}',", sourceDatabase);
					rc.AppendFormat("\n\t@on_success_action=3");
					rc.AppendFormat("\n");
					//lastTable = tbl;
					lastStep++;
				}

			if (lastStep != 0)
			{
				rc.AppendFormat("\nEXEC sp_update_jobstep @job_name='{0}',", objectName);
				rc.AppendFormat("\n\t@step_id={0},", lastStep);
				rc.AppendFormat("\n\t@on_success_action=1");
				rc.AppendFormat("\n");
			}
			rc.AppendFormat("\n");

			// === Add job to the server ===========================================
			rc.AppendFormat("\n---------------------------------------");
			rc.AppendFormat("\n-- Step 4. Add the job to the server --");
			rc.AppendFormat("\n---------------------------------------");
			rc.AppendFormat("\nEXEC sp_add_jobserver @job_name='{0}'", objectName);
			rc.AppendFormat("\n");

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			if (lastStep != 0)
			{
				rc.AppendFormat("\n");
				Constants.AddScriptItem(script, rc.ToString());
				Constants.AddScriptItem(script, "\nGO");
			}
			if (OnPostProcess != null) OnPostProcess(this, new ProcessEventArgs("Job scripted"));
		}

		private void ForceInstall()
		{
			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			bool changed = true;
			while (changed)
			{
				changed = false;
				foreach (Table tbl in tables.Values)
					if (tbl.GetResultingStatus() == Statuses.Installed)
						foreach (Dependency dep in dependencies)
							if ((dep.PrimarySchema == tbl.PrimarySchema) && (dep.PrimaryTable == tbl.PrimaryTable) && (dep.ForeignObject.GetResultingStatus() == Statuses.NotInstalled))
							{
								dep.ForeignObject.SetAction(Actions.Install);
								changed = true;
							}
			}

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
		}

		private void AddCommentLine(List<string> script, string line)
		{
			int chunk;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			chunk = 73;

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			for (int i = 0; i < line.Length; i += chunk)
				script.Add(String.Format(" * {0}{1} *",
					line.Substring(i, (chunk + i > line.Length ? line.Length % chunk : chunk)),
					new string(' ', 73 - (chunk + i > line.Length ? line.Length % chunk : chunk))));
		}
		#endregion
	}
}
